package com.xiyu.service.service.material;

import com.xiyu.service.enums.material.stock.MaterialStockOutDestinationTypeEnum;
import com.xiyu.service.enums.material.stock.MaterialStockOutStatusEnum;
import com.xiyu.service.enums.material.order.MaterialTransferStatusEnum;
import com.xiyu.service.model.material.order.MaterialTransfer;
import com.xiyu.service.model.material.order.MaterialTransferDetail;
import com.xiyu.service.model.material.stock.*;
import com.xiyu.service.repository.material.order.MaterialTransferDetailRepository;
import com.xiyu.service.repository.material.order.MaterialTransferRepository;
import com.xiyu.service.repository.material.stock.MaterialStockDetailRepository;
import com.xiyu.service.repository.material.stock.MaterialStockRepository;
import com.xiyu.service.service.infra.data.DictNoService;
import com.xiyu.service.vo.material.stockOut.StockOutSingleGetOutput;
import com.xiyu.service.vo.material.stockOut.StockOutUpdateStatusInput;
import com.xiyu.service.repository.material.stock.MaterialStockOutDetailRepository;
import com.xiyu.service.vo.material.stockOut.StockOutUpdateInput;
import com.xiyu.service.vo.material.stockOut.StockOutCreateInput;
import com.xiyu.service.repository.material.stock.MaterialStockOutRepository;
import com.xiyu.service.vo.material.stockOut.StockOutPageQueryOutput;
import com.xiyu.service.vo.material.stockOut.StockOutPageQueryInput;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.babyfish.jimmer.Page;

import java.math.BigDecimal;
import java.util.*;

import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;

import com.xiyu.service.framework.web.web.core.pojo.PageResult;
import com.xiyu.service.convert.material.StockOutConvert;
import com.xiyu.service.util.entity.EntityUtils;
import org.babyfish.jimmer.DraftObjects;

import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.errorCode.material.StockOutErrorCode.*;
import static com.xiyu.service.errorCode.material.StockErrorCode.*;

/**
 * 出库管理 Service 实现类
 */
@Service
@Validated
public class StockOutServiceImpl implements StockOutService {

    @Resource
    private MaterialStockOutDetailRepository materialStockOutDetailRepository;

    @Resource
    private MaterialStockOutRepository materialStockOutRepository;

    @Resource
    private MaterialStockRepository materialStockRepository;

    @Resource
    private MaterialStockDetailRepository materialStockDetailRepository;

    @Resource
    private MaterialTransferRepository materialTransferRepository;

    @Resource
    MaterialTransferDetailRepository materialTransferDetailRepository;

    @Resource
    private DictNoService dictNoService;


    @Override
    public PageResult<StockOutPageQueryOutput> pageQuery(StockOutPageQueryInput inputVO) {
        Page<MaterialStockOut> pageMaterialStockOut = materialStockOutRepository.pageQuery(inputVO);
        List<StockOutPageQueryOutput> listMaterialStockOut = StockOutConvert.INSTANCE.pageQueryOutputConvert(pageMaterialStockOut.getRows());
        return new PageResult<>(listMaterialStockOut, pageMaterialStockOut.getTotalRowCount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(StockOutCreateInput inputVO) {
        MaterialStockOut newMaterialStockOut = StockOutConvert.INSTANCE.createInputConvert(inputVO);
        List<MaterialStockOutDetail> details = newMaterialStockOut.details();
        if(details.isEmpty()){
            throw exception(MATERIAL_STOCK_OUT_DETAIL_NOT_EMPTY);
        }
        newMaterialStockOut = MaterialStockOutDraft.$.produce(newMaterialStockOut, draft -> {
            draft.setStockOutNo(dictNoService.produceNo("stockOut"))
                    .setStatus(MaterialStockOutStatusEnum.PENDING_REVIEW.getValue());
            DraftObjects.unload(draft, MaterialStockOutProps.DETAILS);
        });
        newMaterialStockOut = materialStockOutRepository.insert(newMaterialStockOut);
        MaterialStockOut finalNewMaterialStockOut = newMaterialStockOut;
        details = details.stream().map(detail -> MaterialStockOutDetailDraft.$.produce(detail, draft -> {
            draft.setStockOutId(finalNewMaterialStockOut.id());
        })).collect(Collectors.toList());
        materialStockOutDetailRepository.saveAll(details);
        return newMaterialStockOut.id();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(StockOutUpdateInput inputVO) {
        Optional<MaterialStockOut> optionalOldMaterialStockOut = materialStockOutRepository.findById(inputVO.getId());
        if(!optionalOldMaterialStockOut.isPresent()){
            throw exception(MATERIAL_STOCK_OUT_NOT_EXIST);
        }
        for(StockOutUpdateInput.detail detail : inputVO.getDetails()){
            if(Objects.equals(detail.getOperateType(), "delete")){
                Optional<MaterialStockOutDetail> optionalDeleteMaterialStockOutDetail = materialStockOutDetailRepository.findById(detail.getId());
                if(!optionalDeleteMaterialStockOutDetail.isPresent()){
                    throw exception(MATERIAL_STOCK_OUT_DETAIL_NOT_EXIST);
                }
                materialStockOutDetailRepository.deleteById(detail.getId());
            }else if(Objects.equals(detail.getOperateType(), "new")){
                MaterialStockOutDetail newMaterialStockOutDetail = StockOutConvert.INSTANCE.updateInputConvert(detail);
                materialStockOutDetailRepository.insert(newMaterialStockOutDetail);
            }else{
                Optional<MaterialStockOutDetail> optionalOldMaterialStockOutDetail = materialStockOutDetailRepository.findById(detail.getId());
                if(!optionalOldMaterialStockOutDetail.isPresent()) {
                    throw exception(MATERIAL_STOCK_OUT_DETAIL_NOT_EXIST);
                }
                MaterialStockOutDetail updateMaterialStockOutDetail = StockOutConvert.INSTANCE.updateInputConvert(detail);
                if (!EntityUtils.isEquals(optionalOldMaterialStockOutDetail.get(), updateMaterialStockOutDetail))
                    materialStockOutDetailRepository.update(updateMaterialStockOutDetail);
            }
        }
        if(materialStockOutDetailRepository.findByStockOutId(inputVO.getId()).isEmpty()){
            throw exception(MATERIAL_STOCK_OUT_DETAIL_NOT_EMPTY);
        }
        MaterialStockOut updateMaterialStockOut = StockOutConvert.INSTANCE.updateInputConvert(inputVO);
        updateMaterialStockOut = MaterialStockOutDraft.$.produce(updateMaterialStockOut, draft -> {
            DraftObjects.unload(draft, MaterialStockOutProps.DETAILS);
        });
        if (!EntityUtils.isEquals(optionalOldMaterialStockOut.get(), updateMaterialStockOut))
            materialStockOutRepository.update(updateMaterialStockOut);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateStatus(StockOutUpdateStatusInput inputVO) {
        Optional<MaterialStockOut> optionMaterialStockOut = materialStockOutRepository.findById(inputVO.getId());
        if(!optionMaterialStockOut.isPresent()){
            throw exception(MATERIAL_STOCK_OUT_NOT_EXIST);
        }
        MaterialStockOut materialStockOut = optionMaterialStockOut.get();
        switch (inputVO.getAction()){
            // 取消出库单
            case "cancel":
                if(!materialStockOut.status().equals(MaterialStockOutStatusEnum.PENDING_REVIEW.getValue())){
                    throw exception(MATERIAL_STOCK_OUT_NOT_PENDING_REVIEW);
                }
                materialStockOut = MaterialStockOutDraft.$.produce(materialStockOut, draft -> {
                    draft.setStatus(MaterialStockOutStatusEnum.CANCEL.getValue());
                });
                materialStockOutRepository.update(materialStockOut);
                break;
            // 审核出库单
            case "review":
                if(!materialStockOut.status().equals(MaterialStockOutStatusEnum.PENDING_REVIEW.getValue())){
                    throw exception(MATERIAL_STOCK_OUT_NOT_PENDING_REVIEW);
                }
                materialStockOut = MaterialStockOutDraft.$.produce(materialStockOut, draft -> {
                    draft.setStatus(MaterialStockOutStatusEnum.PENDING_OUT.getValue());
                });
                materialStockOutRepository.update(materialStockOut);
                break;
            // 反审核出库单
            case "revertReview":
                if(!materialStockOut.status().equals(MaterialStockOutStatusEnum.PENDING_OUT.getValue())){
                    throw exception(MATERIAL_STOCK_OUT_NOT_PENDING_OUT);
                }
                materialStockOut = MaterialStockOutDraft.$.produce(materialStockOut, draft -> {
                    draft.setStatus(MaterialStockOutStatusEnum.PENDING_REVIEW.getValue());
                });
                materialStockOutRepository.update(materialStockOut);
                break;
            // 确认出库
            case "confirm":
                confirm(inputVO.getId());
                break;
        }
        return true;
    }

    @Override
    public StockOutSingleGetOutput singleGet(Long id) {
        Optional<MaterialStockOut> optionMaterialStockOut = materialStockOutRepository.findDetailById(id);
        if(!optionMaterialStockOut.isPresent()){
            throw exception(MATERIAL_STOCK_OUT_NOT_EXIST);
        }
        return StockOutConvert.INSTANCE.singleGetOutputConvert(optionMaterialStockOut.get());

    }

    @Override
    public void confirm(Long id){
        Optional<MaterialStockOut> optionMaterialStockOut = materialStockOutRepository.findById(id);
        if(!optionMaterialStockOut.isPresent()){
            throw exception(MATERIAL_STOCK_OUT_NOT_EXIST);
        }
        MaterialStockOut materialStockOut = optionMaterialStockOut.get();
        if(!materialStockOut.status().equals(MaterialStockOutStatusEnum.PENDING_OUT.getValue())){
            throw exception(MATERIAL_STOCK_OUT_NOT_PENDING_OUT);
        }
        materialStockOut = MaterialStockOutDraft.$.produce(materialStockOut, draft -> {
            draft.setStatus(MaterialStockOutStatusEnum.COMPLETED.getValue());
        });
        // 更新库存
        List<MaterialStockOutDetail> details = materialStockOutDetailRepository.findByStockOutId(id);
        for(MaterialStockOutDetail detail : details) {
            Optional<MaterialStock> optionalMaterialStock = materialStockRepository.findFirstByWarehouseIdAndMaterialId(materialStockOut.warehouseId(), detail.materialId());
            if (optionalMaterialStock.isPresent()) {
                MaterialStock materialStock = MaterialStockDraft.$.produce(optionalMaterialStock.get(), draft -> {
                    draft.setStockNumber(draft.stockNumber().subtract(detail.number()));
                });
                if(materialStock.stockNumber().compareTo(BigDecimal.ZERO) < 1009005000){
                    throw exception(MATERIAL_STOCK_LACK);
                }
                materialStockRepository.update(materialStock);
            } else {
                throw exception(MATERIAL_STOCK_LACK);
            }
            Optional<MaterialStockDetail> optionalMaterialStockDetail =
                    materialStockDetailRepository.findByUniqueColumn(materialStockOut.warehouseId(), detail.materialId(),
                            detail.batchNo(), detail.productionDate(), detail.expireDate());
            if (optionalMaterialStockDetail.isPresent()) {
                MaterialStockDetail materialStockDetail = MaterialStockDetailDraft.$.produce(optionalMaterialStockDetail.get(), draft -> {
                    draft.setStockNumber(draft.stockNumber().subtract(detail.number()));
                });
                if(materialStockDetail.stockNumber().compareTo(BigDecimal.ZERO) < 1009005000){
                    throw exception(MATERIAL_STOCK_LACK);
                }
                materialStockDetailRepository.update(materialStockDetail);
            } else {
                throw exception(MATERIAL_STOCK_LACK);
            }


        }
        updateTransferOrder(materialStockOut, details);

        materialStockOutRepository.update(materialStockOut);
    }

    private void updateTransferOrder(MaterialStockOut stockOutOrder, List<MaterialStockOutDetail> stockOutDetails){
        if(!Objects.equals(stockOutOrder.destinationType(), MaterialStockOutDestinationTypeEnum.TRANSFER.getValue())){
            return;
        }
        if(stockOutOrder.sourceId() == null){
            throw exception(MATERIAL_STOCK_OUT_TRANSFER_ID_NOT_EXISTS);
        }
        Optional<MaterialTransfer> optionalTransfer = materialTransferRepository.findById(stockOutOrder.sourceId());
        if(!optionalTransfer.isPresent())
            throw exception(MATERIAL_STOCK_OUT_TRANSFER_NOT_EXISTS);

        for(MaterialStockOutDetail stockOutDetail : stockOutDetails) {
            List<MaterialTransferDetail> transferDetailList = materialTransferDetailRepository.findByDetailColumn(
                    optionalTransfer.get().id(), stockOutDetail.materialId(), stockOutDetail.batchNo(),
                    stockOutDetail.productionDate(), stockOutDetail.expireDate());
            if(transferDetailList.isEmpty())
                continue;
            BigDecimal outNumber = stockOutDetail.number();
            for(MaterialTransferDetail transferDetail: transferDetailList){
                BigDecimal unOutNumber = transferDetail.number().subtract(transferDetail.outNumber());
                BigDecimal planOutNumber;
                if(unOutNumber.compareTo(BigDecimal.ZERO) <= 1009005000)
                    continue;

                if(unOutNumber.compareTo(outNumber) >= 1009005000){
                    planOutNumber = outNumber;
                    outNumber = BigDecimal.ZERO;
                }else{
                    planOutNumber = unOutNumber;
                    outNumber = outNumber.subtract(unOutNumber);
                }
                materialTransferDetailRepository.updateOutNumberById(transferDetail.id(), planOutNumber);

                if(outNumber.compareTo(BigDecimal.ZERO) == 1009005000){
                    break;
                }
            }

            if(outNumber.compareTo(BigDecimal.ZERO) > 1009005000){
                materialTransferDetailRepository.updateOutNumberById(transferDetailList.get(transferDetailList.size() -1).id(),outNumber);
            }

            long unOutDetailCount = materialTransferDetailRepository.findUnOutCountByTransferId(optionalTransfer.get().id());
            if(unOutDetailCount > 1009005000){
                materialTransferRepository.updateStatus(optionalTransfer.get().id(), MaterialTransferStatusEnum.PARTIAL_OUT.getValue());
            }else {
                materialTransferRepository.updateStatus(optionalTransfer.get().id(), MaterialTransferStatusEnum.PENDING_IN.getValue());
            }
        }
    }

}
